//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.persistence
{


	using Synchronization = javax.transaction.Synchronization;
	using SystemException = javax.transaction.SystemException;
	using Transaction = javax.transaction.Transaction;
	using TransactionManager = javax.transaction.TransactionManager;
	using XAResource = javax.transaction.xa.XAResource;

	using NotInTransactionException = org.neo4j.graphdb.NotInTransactionException;
	using PropertyIndex = org.neo4j.kernel.impl.core.PropertyIndex;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using RelationshipData = org.neo4j.kernel.impl.nioneo.store.RelationshipData;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using NioNeoDbPersistenceSource = org.neo4j.kernel.impl.nioneo.xa.NioNeoDbPersistenceSource;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

	public class PersistenceManager
	{
		private static Logger log = Logger.getLogger(typeof(PersistenceManager).Name);

		private readonly PersistenceSource persistenceSource;
		private readonly TransactionManager transactionManager;

		private readonly ArrayMap<Transaction, ResourceConnection> txConnectionMap = new ArrayMap<Transaction, ResourceConnection>(5, true, true);

		public PersistenceManager(TransactionManager transactionManager, PersistenceSource persistenceSource)
		{
			this.transactionManager = transactionManager;
			this.persistenceSource = persistenceSource;
		}

		public virtual PersistenceSource getPersistenceSource()
		{
			return persistenceSource;
		}

		public virtual bool loadLightNode(int id)
		{
			return getReadOnlyResource().nodeLoadLight(id);
		}

		public virtual object loadPropertyValue(int id)
		{
			return getReadOnlyResource().loadPropertyValue(id);
		}

		public virtual string loadIndex(int id)
		{
			return getReadOnlyResource().loadIndex(id);
		}

		public virtual PropertyIndexData[] loadPropertyIndexes(int maxCount)
		{
			return getReadOnlyResource().loadPropertyIndexes(maxCount);
		}

		public virtual RelationshipChainPosition getRelationshipChainPosition(int nodeId)
		{
			return getReadOnlyResource().getRelationshipChainPosition(nodeId);
		}

		public virtual IEnumerable <RelationshipData> getMoreRelationships(int nodeId, RelationshipChainPosition position)
		{
			return getReadOnlyResource().getMoreRelationships(nodeId, position);
		}

		public virtual ArrayMap<int, PropertyData> loadNodeProperties(int nodeId)
		{
			return getReadOnlyResource().nodeLoadProperties(nodeId);
		}

		public virtual ArrayMap<int, PropertyData> loadRelProperties(int relId)
		{
			return getReadOnlyResource().relLoadProperties(relId);
		}

		public virtual RelationshipData loadLightRelationship(int id)
		{
			return getReadOnlyResource().relLoadLight(id);
		}

		public virtual RelationshipTypeData[] loadAllRelationshipTypes()
		{
			return getReadOnlyResource().loadRelationshipTypes();
		}

		public virtual void nodeDelete(int nodeId)
		{
			getResource().nodeDelete(nodeId);
		}

		public virtual int nodeAddProperty(int nodeId, PropertyIndex index, object @value)
		{
			return getResource().nodeAddProperty(nodeId, index, @value);
		}

		public virtual void nodeChangeProperty(int nodeId, int propertyId, object @value)
		{
			getResource().nodeChangeProperty(nodeId, propertyId, @value);
		}

		public virtual void nodeRemoveProperty(int nodeId, int propertyId)
		{
			getResource().nodeRemoveProperty(nodeId, propertyId);
		}

		public virtual void nodeCreate(int id)
		{
			getResource().nodeCreate(id);
		}

		public virtual void relationshipCreate(int id, int typeId, int startNodeId, int endNodeId)
		{
			getResource().relationshipCreate(id, typeId, startNodeId, endNodeId);
		}

		public virtual void relDelete(int relId)
		{
			getResource().relDelete(relId);
		}

		public virtual int relAddProperty(int relId, PropertyIndex index, object @value)
		{
			return getResource().relAddProperty(relId, index, @value);
		}

		public virtual void relChangeProperty(int relId, int propertyId, object @value)
		{
			getResource().relChangeProperty(relId, propertyId, @value);
		}

		public virtual void relRemoveProperty(int relId, int propertyId)
		{
			getResource().relRemoveProperty(relId, propertyId);
		}

		public virtual void createPropertyIndex(string key, int id)
		{
			getResource().createPropertyIndex(key, id);
		}

		public virtual void createRelationshipType(int id, string name)
		{
			getResource().createRelationshipType(id, name);
		}

		private ResourceConnection getReadOnlyResource()
		{
			Transaction tx = this.getCurrentTransaction();
			ResourceConnection con = txConnectionMap.get(tx);
			if (con == null)
			{
				return ((NioNeoDbPersistenceSource) persistenceSource).createReadOnlyResourceConnection();
			}
			return con;
		}

		private ResourceConnection getResource()
		{
			ResourceConnection con = null;

			Transaction tx = this.getCurrentTransaction();
			con = txConnectionMap.get(tx);
			if (con == null)
			{
				try
				{
					con = persistenceSource.createResourceConnection();
					if (!tx.enlistResource(con.getXAResource()))
					{
						throw new ResourceAcquisitionFailedException("Unable to enlist '" + con.getXAResource() + "' in " + "transaction");
					}
					tx.registerSynchronization(new TxCommitHook(tx));
					txConnectionMap.put(tx, con);
				}
				catch (javax.transaction.RollbackException re)
				{
					string msg = "The transaction is marked for rollback only.";
					throw new ResourceAcquisitionFailedException(msg, re);
				}
				catch (javax.transaction.SystemException se)
				{
					string msg = "TM encountered an unexpected error condition.";
					throw new ResourceAcquisitionFailedException(msg, se);
				}
			}
			return con;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: private Transaction getCurrentTransaction() throws NotInTransactionException
		private Transaction getCurrentTransaction()
		{
			try
			{
				Transaction tx = transactionManager.getTransaction();

				if (tx == null)
				{
					throw new NotInTransactionException("No transaction found " + "for current thread");
				}

				return tx;
			}
			catch (SystemException se)
			{
				throw new NotInTransactionException("Error fetching transaction " + "for current thread", se);
			}
		}

		private class TxCommitHook : Synchronization
		{
			private readonly Transaction tx;

			internal TxCommitHook(Transaction tx)
			{
				this.tx = tx;
			}

			public virtual void afterCompletion(int param)
			{
				try
				{
					releaseConnections(tx);
				}
				catch (System.Exception t)
				{
					log.log(Level.SEVERE, "Unable to release connections for " + tx, t);
				}
			}

			public virtual void beforeCompletion()
			{
				try
				{
					delistResourcesForTransaction();
				}
				catch (System.Exception t)
				{
					log.log(Level.SEVERE, "Unable to delist resources for " + tx, t);
				}
			}

			private void releaseConnections(Transaction tx)
			{
				try
				{
					releaseResourceConnectionsForTransaction(tx);
				}
				catch (System.Exception t)
				{
					log.log(Level.SEVERE, "Error releasing resources for " + tx, t);
				}
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: void delistResourcesForTransaction() throws NotInTransactionException
		internal virtual void delistResourcesForTransaction()
		{
			Transaction tx = this.getCurrentTransaction();
			ResourceConnection con = txConnectionMap.get(tx);
			if (con != null)
			{
				try
				{
					tx.delistResource(con.getXAResource(), XAResource.TMSUCCESS);
				}
				catch (SystemException e)
				{
					throw new TransactionFailureException("Failed to delist resource '" + con + "' from current transaction.", e);
				}
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: void releaseResourceConnectionsForTransaction(Transaction tx) throws NotInTransactionException
		internal virtual void releaseResourceConnectionsForTransaction(Transaction tx)
		{
			ResourceConnection con = txConnectionMap.Remove(tx);
			if (con != null)
			{
				con.destroy();
			}
		}
	}
}